﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MSAS.FrontEnd.DL;

namespace MRS.FrontEnd.BO
{
    /// <summary>
    /// Base class to implement a reputation codec
    /// This class provides the common portion of the coding, decoding that is extended
    /// by the specialization classes
    /// </summary>
    public class ReputationBinaryCodecBase 
    {
        /// <summary>
        /// Start position within the fixed portion of the binary
        /// </summary>

        protected enum FixedPosition
        {
            Sha256 = 1,
            Version = 33,
            Cats = 41,
            Flags = 42,
            Expiration = 43
        }

        protected const int FixedPortionLength = 44;        // The length of the fixed portion of the codec
        private const int Sha256Length = 32;                // The length of a Sha256 in bytes

        // store the DL.ICache to resolve any of the expiration times
        internal static ICache cache;

        /// <summary>
        /// Decode the fixed portion of the reputation binary.  Class derived from this will call on this method
        /// </summary>
        /// <param name="bytes">bytes to decode, must be at least 41 in length</param>
        /// <param name="unpack">Unpack object to receive the decoding</param>
        /// <remarks>
        /// The fixed portion of the reputation binary is always the same for all object types
        /// 
        /// | type (1 byte)| SHA256 (32 bytes)| version (8 bytes)| flags (1 byte)| expiration (1 byte)
        /// </remarks>
        protected static void DecodeFixedPortion(byte[] bytes, UnpackedObject unpack)
        {
            // validate
            if (bytes.Length < FixedPortionLength)
            {
                throw new ArgumentOutOfRangeException("bytes");
            }

            if (unpack == null)
            {
                throw new ArgumentNullException("unpack");
            }

            // extend the types as new codec are implemented
            switch (bytes.First())
            {
                case 4: 
                    unpack.Type = ObjectType.URL;
                    break;

                default:
                    throw new NotImplementedException(
                        string.Format("{0} is not a recognized object type", bytes.First())
                        );
            }

            unpack.Sha256 = bytes.Skip((int)FixedPosition.Sha256).Take(Sha256Length).ToArray();
            unpack.Version = BitConverter.ToInt64(bytes.Skip((int)FixedPosition.Version).Take(8).Reverse().ToArray(), 0);
            unpack.Flags = bytes.Skip((int)FixedPosition.Flags).First();

            // get the expiration based on the cache from the database
            unpack.ServerExpirationInMinutes = cache.GetServerExpirationTime(
                (int)unpack.Type, 
                (short)(bytes.Skip((int)FixedPosition.Expiration).First() >> 4)     // high nibble
                );
            unpack.ClientExpirationInMinutes = cache.GetClientExpirationTime(
                (int)unpack.Type, 
                (short)(bytes.Skip((int)FixedPosition.Expiration).First() & 0x0F)   // low nibble
                );
        }

        /// <summary>
        /// Pack the fixed portion of the unpack object in a byte array that should be equivalent to the 
        /// PSDB format
        /// </summary>
        /// <param name="unpack"></param>
        /// <returns></returns>
        protected static byte[] EncodeFixedPortion(UnpackedObject unpack, int categoryCount)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add((byte)unpack.Type);
            bytes.AddRange(unpack.Sha256);
            bytes.AddRange(BitConverter.GetBytes(unpack.Version).Reverse());
            bytes.Add((byte)categoryCount);
            bytes.Add(0x00);    // no flags, the specialized class will put it in
            byte cacheTime = 0;
            cacheTime = cache.GetServerExpirationIndex((int) unpack.Type, unpack.ServerExpirationInMinutes);
            cacheTime <<= 4;
            cacheTime |= cache.GetClientExpirationIndex((int) unpack.Type, unpack.ClientExpirationInMinutes);
            bytes.Add(cacheTime);

            return bytes.ToArray();
        }
    }
}
